/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Almacenamiento;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;

/**
 * GestorIOPagina: Implementa un gestor de pagina de I/O sobre archivos.
 * @author Christian
 */
public class GestorIOPagina {
     /**
     * Construir una nuevo gestor de página de I/O.
     */
    public GestorIOPagina() {
    }

    /**
     * Escribe una página en un archivo de salida
     * 
     * @param raf archivo de salida.
     * @param pagina la página a ser escrita.
     * @throws StorageManagerException thrown whenever there is an
     * output error.
     */
    public static void escribirPagina(RandomAccessFile raf, Pagina pagina) {

        try {
            // buscar el lugar correcto en el archivo
            long seek = pagina.getIdentificadorPagina().getNumero() * 4096;
            raf.seek(seek);
            byte[] bytes = new byte[4096];
            volcarNumero(pagina, bytes);
            volcarTuplas(pagina, bytes);
            //escribir en disco el arreglo de bytes
            raf.write(bytes);
        } catch (IOException ioe) {
            System.out.println("Exception mientras se escribe la página "
                    + pagina.getIdentificadorPagina()
                    + " a disco: " + ioe);
        }
    }

    /**
     * Leer una página del disco.
     * 
     * @param relacion la relación a la que la página pertenece.
     * @param pid Identificador de la página.
     * @param raf el archivo de la salida.
     * @return la página leida.
     */
    public static Pagina leerPagina(Relacion relacion, IdentificadorPagina pid, RandomAccessFile raf) {
        try {
            // seek to the appropriate place
            long seek = pid.getNumero() * 4096;
            raf.seek(seek);
            byte[] bytes = new byte[4096];
            int leerBytes = raf.read(bytes);
            if (leerBytes == -1) {
                // hemos llegado al final del archivo, 
                // así que tenemos que asignar una página
                raf.setLength(seek + 4096);
                return new Pagina(relacion, pid);
            }
            if (leerBytes != 4096) {
                System.out.println("Pagina: " + pid.toString()
                        + "no fue leido completamente.");
            }
            return extraerTuplas(relacion, pid, bytes);
        } catch (IOException ioe) {
            System.out.println("Exception mientras se lee la página "
                    + pid.toString()
                    + " de disco. " + ioe);
            return null;
        }
    }

    /**
     * Volcar al arreglo de bytes el número de tuplas en la página.
     *
     * @param pagina la página a ser escrita.
     * @param bytes un arreglo de salida de bytes.     
     */
    protected static void volcarNumero(Pagina pagina, byte[] bytes) {

        byte[] b = Convert.toByte(pagina.getNumeroDeTuplas());
        System.arraycopy(b, 0, bytes, 0, b.length);
    }

    /**
     * Volcar a arreglo de bytes una página de tuplas a disco.
     * 
     * @param pagina la página a ser escrito en el disco.
     * @param bytes el arreglo de bytes de salida para las tuplas.
     */
    protected static void volcarTuplas(Pagina pagina, byte[] bytes) {

        // crear un nuevo gestor IO de tuplas
        GestorIOTupla gestor =
                new GestorIOTupla(pagina.getRelacion(),
                pagina.getIdentificadorPagina().getNombreArchivo());
        // Un numero entero se utiliza para el numero de tuplas
        int desplazamiento = Convert.INT_SIZE;
        // iterate sobre todas las tuplas de la página, y localizarlo en el arreglo
        for (Tupla tupla : pagina) {
            desplazamiento = gestor.escribirTupla(tupla, bytes, desplazamiento);
        }
        rellenar(bytes, desplazamiento);
    }

    /**
     * Leer las tuplas del disco y los coloca en una página.
     * 
     * @param relacion la relacion a la que pertenece la página.
     * @param pid el identificador de la nueva página.
     * @param bytes el arreglo de bytes donde las tuplas se encuentran.
     * @return la pagina leida del disco.
     */
    protected static Pagina extraerTuplas(Relacion relacion, IdentificadorPagina pid,
            byte[] bytes) {

        // crear un gestor IO de tupla
        GestorIOTupla gestor = new GestorIOTupla(relacion,
                pid.getNombreArchivo());
        // iniciar la lectura de tuplas
        int numeroDeTuplas = extraerNumero(bytes);
        Pagina pagina = new Pagina(relacion, pid);
        // Un numero entero se utiliza para el numero de tuplas
        int desplazamiento = Convert.INT_SIZE;
        for (int i = 0; i < numeroDeTuplas; i++) {
            Par par = gestor.leerTupla(bytes, desplazamiento);
            Tupla tupla = (Tupla) par.primero;
            desplazamiento = ((Integer) par.segundo).intValue();
            pagina.adicionarTupla(tupla);
        }

        return pagina;
    }

    /**
     * Extraer numero de tuplas del arreglo de byte.
     *
     * @param bytes el arreglo de bytes.
     * @return el numero de tuplas.
     */
    public static int extraerNumero(byte[] bytes) {

        byte[] b = new byte[Convert.INT_SIZE];
        System.arraycopy(bytes, 0, b, 0, b.length);
        return Convert.toInt(b);
    }

    /**
     * Rellena una matriz de bytes con ceros para alcanzar 
     * el tamaño de página de disco.  
     * 
     * @param bytes el arreglo de bytes de entrada a ser rellenado.
     * @param inicio el inicio del desplazamiento en el arreglo de bytes.
     */
    protected static void rellenar(byte[] bytes, int inicio) {
        for (int i = inicio; i < bytes.length; i++) {
            bytes[i] = (byte) 0;
        }
    }
    /**
     * Metodo para crear cualquier esquema de una relacion
     * @param NroCampos numero de atributos que puede tener una realcion
     * @return reyorna una relacion
     * @throws IOException 
     */
    public static Relacion CrearEsquema_Relacion(int NroCampos)throws IOException{
        Scanner Lector= new Scanner(System.in); 
        Relacion R=new Relacion();
        for (int i = 0; i < NroCampos; i++) {
            System.out.println("-------ATRIBUTO--->"+i);
            System.out.println("************************");
            System.out.print("Nombre Atributo :");
            String NombreAtributo=Lector.next();
            System.out.print("Tipo Atributo(Character,String,Integer,Short,Float,Double,Byte,Long) :");
            String TipoAtributo= String.valueOf(Lector.next());
            Atributo A=null;
            switch(TipoAtributo){
                case "Character":A=new Atributo(NombreAtributo, Character.class);break;
                case "Byte":A=new Atributo(NombreAtributo, Byte.class);break;
                case "Short":A=new Atributo(NombreAtributo, Short.class);break;
                case "Integer":A=new Atributo(NombreAtributo, Integer.class);break;
                case "Long":A=new Atributo(NombreAtributo, Long.class);
                case "Float":A=new Atributo(NombreAtributo, Float.class);break;
                case "Double":A=new Atributo(NombreAtributo, Double.class);break;
                case "String":A=new Atributo(NombreAtributo, String.class);break;
                    default:System.out.print("error al ingresar el tipo");break;
            }
            R.adicionarAtributo(A);
        }
        return R;
    }
    /**
     * Metodo para ingresar el numero de tuplas y sus respectivos valores
     * @param v Lista de valores
     * @param Nro numero de tuplas que puede tener la relacion
     * @param R es la relacion
     * @param Nombre_Relacion nombre de la realcion
     * @throws IOException 
     */
    public static List<Tupla> CrearTuplas_Con_Valores(int Nro,Relacion R,String Nombre_Relacion) throws IOException{
        Scanner Lector= new Scanner(System.in); 
        List<Comparable> v=new ArrayList<Comparable>();
        //insertando valores a la relacion y creando las tuplas
        List<Tupla> ListaTuplas=new ArrayList<Tupla>();
        for(int i=0;i<Nro;i++){
            System.out.println("=================================================");
            System.out.println("INGRESE LOS VALORES DE LA TUPLA : <"+i+">");
            System.out.println("=================================================");
            for (int j = 0; j < R.getNumeroDeAtributos(); j++) {
                Atributo A=R.getAtributo(j);
                String tip =(String.valueOf(A.getTipo()));
                switch(tip){
                    case "class java.lang.Character":System.out.print(A.getNombre()+" :");char c=(char)System.in.read();
                                                     v.add(new Character(c));break;
                    case "class java.lang.Byte":System.out.print(A.getNombre()+" :");String b=Lector.next();
                                                v.add(new Byte(Byte.parseByte(b)));break;
                    case "class java.lang.Short":System.out.print(A.getNombre()+" :");String s=Lector.next();
                                                 v.add(new Short(Short.parseShort(s)));break;
                    case "class java.lang.Integer":System.out.print(A.getNombre()+" :");String in=Lector.next();
                                                   v.add(new Integer(Integer.parseInt(in)));break;
                    case "class java.lang.Long":System.out.print(A.getNombre()+" :");String l=Lector.next();
                                                     v.add(new Long(Long.parseLong(l)));break;
                    case "class java.lang.Float":System.out.print(A.getNombre()+" :");String f=Lector.next();
                                                     v.add(new Float(Float.parseFloat(f)));break;
                    case "class java.lang.Double":System.out.print(A.getNombre()+" :");String d=Lector.next();
                                                     v.add(new Double(Double.parseDouble(d)));break;
                    case "class java.lang.String":System.out.print(A.getNombre()+" :");
                                                     v.add(new String(Lector.nextLine()));break;
                    default:
                        System.out.print("error al ingresar los valores");break;
                }
            }         
            //crear tuplas
            IdentificadorTupla T=new IdentificadorTupla(Nombre_Relacion, i);
            Tupla t = new Tupla(T, v);
            ListaTuplas.add(i, t);
            v=new ArrayList<Comparable>();
        }
        return ListaTuplas;
    }
    /**
     * 
     * @param NombreArchivo nombre del archivo donde se va almacenar
     * @param NroPag numero de paginas a crear
     * @param R esquema de relacion
     * @param v es la lista de valores
     * @return
     * @throws IOException 
     */
    public static void Agregar_Tuplas_APagina(List<Tupla> ListaTuplas,String NombreArchivo,Relacion R) throws IOException{
        IdentificadorPagina pid = new IdentificadorPagina(NombreArchivo, 0);
        Pagina p = new Pagina(R, pid);
        for (int i = 0; i <ListaTuplas.size(); i++) {
            if(p.hayEspacio(ListaTuplas.get(i)))
                p.adicionarTupla(ListaTuplas.get(i));
            else
            {
                IdentificadorPagina pid_nuevo = new IdentificadorPagina(NombreArchivo, 1);
                p=new Pagina(R, pid_nuevo);
                p.adicionarTupla(ListaTuplas.get(i));
            }
        }
    }
    public static void Almacenar_Pagina_En_Disco(Pagina P,String nombreArchivo){
        try {
            java.io.RandomAccessFile raf = new java.io.RandomAccessFile(nombreArchivo, "rw");
            GestorIOPagina.escribirPagina(raf,P);
            raf.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(GestorIOPagina.class.getName()).log(Level.SEVERE, null, ex);
        } catch(IOException e){
            System.out.printf(e.toString());
        }
    }
    public static void Leer_Pagina_Del_Disco(Pagina P,String nombreArchivo,Relacion R){
        try {
            java.io.RandomAccessFile raf = new java.io.RandomAccessFile(nombreArchivo, "r");
            System.out.println("leyendo pagina...");
            Pagina pagina = GestorIOPagina.leerPagina(R, new IdentificadorPagina(nombreArchivo, P.getIdentificadorPagina().getNumero()), raf);
            System.out.println(pagina);
            raf.close();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(GestorIOPagina.class.getName()).log(Level.SEVERE, null, ex);
        } catch(IOException e){
            System.out.print(e.toString());
        }
    }
    public static void main(String[] args) {
        try {
            Scanner leer=new Scanner(System.in);
            //direccion del archivo
            String nombreArchivo = "D:/prueba/relacion.dat";
            // Esquema de relación
            System.out.print("Ingrese el numero de atributos :");
            int NroCampos=leer.nextInt();
            Relacion R = CrearEsquema_Relacion(NroCampos);
            // Crear una lista de tuplas con valores
            System.out.print("Ingrese el numero de tuplas :");
            int NroTuplas=leer.nextInt();
            List<Tupla> T=CrearTuplas_Con_Valores(NroTuplas, R, nombreArchivo);
            //almacenar tuplas en una pagina de 4kb
            Agregar_Tuplas_APagina(T, nombreArchivo, R);
            // Almacenar pagina en disco
            
            // Leer la página del disco
        } catch (Exception e) {
            System.err.println("Exception: " + e.getMessage());
            e.printStackTrace(System.err);
        }
    }
}
